package scalab.core.interpreter

import scalab.utils.{Digest, MD5, Hexadecimal, Reporter}
import scalab.core.component.{Universe, SinkComponent, Component, Target}
import scalab.core.gates.Input
import scalab.core.utils.{AlgorithmFactory, IncidenceMatrix}
import scala.collection.immutable.{SortedSet, TreeSet, HashMap, HashSet}
import java.lang.reflect.Method

trait Build{
	
	private def getTarget(targetName: String): Option[Target] = {
	/*TODO: 
		check access modifiers (currently an exception is thrown)
		*/
		try{
			if(targetName.indexOf(".") == -1){
				val cls = Class.forName("BuildGraph")
				var getter: Method = cls.getMethod(targetName, Array())
				Some(getter.invoke(null, Array()).asInstanceOf[Target])
			}
			else{
				//this makes the build developer the only person who decides targets 
				//that is targets defined in components are not valid!
				Reporter.error("Invalid target name: '" +targetName + "'! Targets must be top level definitions.")
				None
			}
		}
		catch{
			case nsme: NoSuchMethodException =>
				Reporter.error("Unknown target: " + targetName + "!")
				None
		}
	}

	def main(args: Array[String]): Unit = {
		import java.lang.reflect.InvocationTargetException
		try{
			if(args.length == 1){
				val opt = getTarget(Settings.targetName)
				if(!opt.isEmpty){
					val target = opt.get
					if(target.validate){ //validate graph
						Build.computeIdentifiers //compute components ids
						Build.reverseListeners
						Build.fireBuildStarted(target)
						val res = target.update //update target
						Build.fireBuildFinished(target, res)
						if(res)Reporter.succes("Build succeeded.")
						else Reporter.error("Build failed!")
					}
				}
			}
			else{
				if(args.length == 0)
					Reporter.error("Did not get any target to build!")
				else
					Reporter.error("Cannot build multiple targets. This restriction might disappear in the future.")
			}
		}
		catch{
			case ite: InvocationTargetException =>
				throw ite.getCause
		}
	}
}

object Build{
	import scalab.core.event._

	private var buildStartedListeners: List[Listener[BuildStarted]] = Nil
	private var buildFinishedListeners: List[Listener[BuildFinished]] = Nil

	private def reverseListeners{
		buildStartedListeners = buildStartedListeners.reverse
		buildFinishedListeners = buildFinishedListeners.reverse
		Cache.getCaches foreach {c => c.reverseListeners}
		Buildable.getAll foreach {m => m.reverseListeners}
	}
	
	def listenBuildStarted(listener: BuildStarted => Unit){
			buildStartedListeners = Listener[BuildStarted](listener) :: buildStartedListeners
	}

	def listenBuildFinished(listener: BuildFinished => Unit){
			buildFinishedListeners = Listener[BuildFinished](listener) :: buildFinishedListeners
	}


	private def fireBuildStarted(target: Target){
		val event = BuildStarted(target)
		buildStartedListeners foreach {l => l(event)}
	}

	private def fireBuildFinished(target: Target, res: Boolean){
		val event = BuildFinished(target, res)
		buildFinishedListeners foreach {l => l(event)}
	}

	private def computeIdentifiers{
		val sortedComponents = computeIndexes
		val inputIndexes = computeInputIndexes(sortedComponents)
		val componentsDeps = 
			AlgorithmFactory.createComponentsDeps(sortedComponents)
		val matrix = new IncidenceMatrix(sortedComponents, inputIndexes, componentsDeps)
		sortedComponents foreach {c =>
			c.identifier = matrix.encodeComponent(c, sortedComponents)
			//c.info("id = " + c.id)
		}
	}

	private def computeIndexes: SortedSet[Component] = {
		val sorted = 
			(new TreeSet[Component]()(Component.natureOrdered _)) ++ Component.getAll + Universe
		var index = 1
		sorted foreach {c =>
			if(c eq Universe)
				c.index = 0
			else{
				c.index = index
				index += 1
			}
		}
		sorted
	}

	def computeInputIndexes(components: SortedSet[Component]): Map[Input, Int] = {
		var indexes: Map[Input, Int] = HashMap.empty[Input, Int]
		var index = 1 //0 reserved by Universe
		components foreach {c =>
			if(c != Universe){
				c.asInstanceOf[SinkComponent].inputs foreach{i =>
					indexes = indexes.update(i, index)
					index += 1
				}
			}
		}
		indexes
	}
}


